package com.mz361.stdapi.service.impl;


import com.alibaba.fastjson.JSONObject;
import com.mz361.common.exception.ResultException;
import com.mz361.common.utils.DateUtils;
import com.mz361.common.utils.RedisUtil;
import com.mz361.common.utils.StringUtil;
import com.mz361.component.jwt.enums.JwtResultEnums;
import com.mz361.modules.system.domain.CMember;
import com.mz361.modules.system.service.CMemberService;
import com.mz361.stdapi.domain.*;
import com.mz361.stdapi.service.BasicSearchService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author niceale
 * @ date 2024/04/26
 */
@Service
@Slf4j
public class BasicSearchServiceImpl implements BasicSearchService {
    // 高亮显示的前缀标签，用于给匹配的文本添加HTML样式使其变红
    private static final String PRE_TAG = "<font color='red'>";
    // 高亮显示的后缀标签，用于结束添加HTML样式的部分
    private static final String POST_TAG = "</font>";
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    RedisUtil redisUtil;
    @Autowired
    private CMemberService cMemberService;

    /**
     * 基础检索接口
     *
     * @param basicSearch
     * @ return
     */

    @Override
    public BasicResult basicSearch(String userId, BasicSearch basicSearch) {
        basicSearch.setFlag(false);
        if (StringUtil.notEmpty(userId)) {
            CMember cMember = cMemberService.getById(Long.valueOf(userId));
            if (null == cMember) {
                throw new ResultException(JwtResultEnums.AUTH_REQUEST_NULLUSERERROR);
            }
            if (StringUtil.notEmpty(basicSearch.getKeyword())) {
                redisUtil.incrementKeyword("SORT:STANDARDSEARCH:KEYWORD:" + userId, basicSearch.getKeyword());
            }
        }
        addSortRedis(basicSearch);
        return v2basicSearch("", basicSearch);
    }

    @Override
    public BasicResult gbFreeSearch(String userId, BasicSearch basicSearch) {
        basicSearch.setHavePdf("1");
        basicSearch.setStandardClass("CN");
        basicSearch.setFlag(true);
        addSortRedis(basicSearch);
        return v2basicSearch("free", basicSearch);
    }

    @Override
    public BasicResult v2basicSearch(String userId, BasicSearch basicSearch) {
        SearchRequest searchRequest = new SearchRequest("standard_info");
        SearchSourceBuilder searchSourceBuilder = buildSearchSourceBuilder(basicSearch);
        searchRequest.source(searchSourceBuilder);
        try {
            // 执行搜索请求
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            return processSearchResponse(response, basicSearch);
        } catch (Exception e) {
            log.error("exception ", e);
        }

        return null;
    }

    // 根据传入的BasicSearch参数构建SearchSourceBuilder，设置查询条件、聚合、分页、排序、高亮等信息
    private SearchSourceBuilder buildSearchSourceBuilder(BasicSearch basicSearch) {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = buildBoolQuery(basicSearch);
        searchSourceBuilder.query(boolQueryBuilder);

        // 设置聚合操作，按照fn_StandClass进行分组聚合，并且在其内部再按照fn_ccsMark和fn_StandType进行子聚合
        searchSourceBuilder.aggregation(AggregationBuilders.terms("fn_StandClassAgg")
                .field("fn_StandClass.keyword")
                .subAggregation(AggregationBuilders.terms("fn_CCSAgg")
                        .field("fn_ccsMark.keyword")
                        .size(50))
                .subAggregation(AggregationBuilders.terms("fn_TypeAgg")
                        .field("fn_StandType.keyword")
                        .size(50)));

        int pageSize = basicSearch.getPageSize();
        int from = (basicSearch.getPageNo() - 1) * pageSize;
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(pageSize);
        searchSourceBuilder.sort("id", SortOrder.DESC);//按实施时间倒序

        HighlightBuilder highlightBuilder = buildHighlightBuilder();
        searchSourceBuilder.highlighter(highlightBuilder);

        return searchSourceBuilder;
    }

    // 构建布尔查询条件，根据BasicSearch中的不同条件来构建相应的查询逻辑
    private BoolQueryBuilder buildBoolQuery(BasicSearch basicSearch) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        // 根据关键词构建查询条件
        if (StringUtil.notEmpty(basicSearch.getKeyword())) {
            BoolQueryBuilder keywordBoolQueryBuilder = buildKeywordQuery(basicSearch.getKeyword());
            boolQueryBuilder.must(keywordBoolQueryBuilder);
        }

        // 根据修改日期构建查询条件
        if (StringUtil.notEmpty(basicSearch.getModiDate())) {
            List<String> modiDates = getModiDateList(basicSearch.getModiDate());
            BoolQueryBuilder modiDateBoolQueryBuilder = buildModiDateQuery(modiDates);
            boolQueryBuilder.must(modiDateBoolQueryBuilder);
        }

        // 根据标准分类构建查询条件
        if (StringUtil.notEmpty(basicSearch.getStandardClass())) {
            boolQueryBuilder.must(QueryBuilders.matchPhraseQuery("fn_StandClass", basicSearch.getStandardClass()));
        }

        // 根据起草人构建查询条件
        if (StringUtil.notEmpty(basicSearch.getDraftsMan())) {
            boolQueryBuilder.must(QueryBuilders.matchPhraseQuery("fn_draftsMan", basicSearch.getDraftsMan()));
        }

        // 根据起草部门构建查询条件
        if (StringUtil.notEmpty(basicSearch.getDraftsDept())) {
            boolQueryBuilder.must(QueryBuilders.matchPhraseQuery("fn_draftsDept", basicSearch.getDraftsDept()));
        }

        // 根据分类代码构建查询条件，根据标准分类的值不同选择不同的字段进行匹配查询
        if (StringUtil.notEmpty(basicSearch.getClassCode())) {
            if (StringUtil.equals(basicSearch.getStandardClass(), "CN")) {
                boolQueryBuilder.filter(QueryBuilders.matchPhraseQuery("fn_ccsMark.keyword", basicSearch.getClassCode()));
            } else {
                boolQueryBuilder.filter(QueryBuilders.matchPhraseQuery("fn_StandType.keyword", basicSearch.getClassCode()));
            }
        }
        if (basicSearch.isFlag()) {

            // 创建专门的布尔查询来处理fn_AdoptNo条件
            BoolQueryBuilder adoptNoQuery = QueryBuilders.boolQuery();

            // 添加条件：匹配fn_AdoptNo为空字符串
            adoptNoQuery.should(QueryBuilders.termQuery("fn_AdoptNo.keyword", ""));

            // 添加条件：匹配fn_AdoptNo字段不存在
            adoptNoQuery.should(QueryBuilders.boolQuery()
                    .mustNot(QueryBuilders.existsQuery("fn_AdoptNo"))
            );

            // 设置至少匹配一个条件（空字符串或不存在）
            adoptNoQuery.minimumShouldMatch(1);

            // 将adoptNoQuery作为must条件添加到主查询中
            boolQueryBuilder.must(adoptNoQuery);
        }

        // 根据是否有PDF构建查询条件
        if (StringUtil.notEmpty(basicSearch.getHavePdf())) {
            boolQueryBuilder.must(QueryBuilders.termQuery("fn_HavePDF.keyword", basicSearch.getHavePdf()));
        }

        // 根据状态构建查询条件
        if (StringUtil.notEmpty(basicSearch.getStatus())) {
            List<String> statusList = getStatusList(basicSearch.getStatus());
            BoolQueryBuilder statusBoolQueryBuilder = buildStatusQuery(statusList);
            boolQueryBuilder.must(statusBoolQueryBuilder);
        }

        return boolQueryBuilder;
    }

    // 构建关键词相关的布尔查询条件，用于匹配fn_A301和fn_A100字段
    private BoolQueryBuilder buildKeywordQuery(String keyword) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.should(QueryBuilders.matchPhraseQuery("fn_A301", keyword))
                .should(QueryBuilders.matchPhraseQuery("fn_A100", keyword))
                .should(QueryBuilders.matchPhraseQuery("fn_draftsDept", keyword));
        return boolQueryBuilder;
    }

    // 将修改日期字符串处理为日期列表，如果包含逗号则分割，否则作为单个元素列表返回
    private List<String> getModiDateList(String modiDateStr) {
        if (StringUtil.isContans(modiDateStr, ",")) {
            return Arrays.asList(modiDateStr.split(","));
        } else {
            return Collections.singletonList(modiDateStr);
        }
    }

    // 将标准状态处理为列表，如果包含逗号则分割，否则作为单个元素列表返回
    private List<String> getStatusList(String status) {
        if (StringUtil.isContans(status, ",")) {
            return Arrays.asList(status.split(","));
        } else {
            return Collections.singletonList(status);
        }
    }

    // 根据修改日期列表构建布尔查询条件，处理"2019及以前"等特殊情况的逻辑
    private BoolQueryBuilder buildModiDateQuery(List<String> modiDates) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        for (String modiDate : modiDates) {
            if (StringUtil.equals(modiDate, "2019及以前")) {
                BoolQueryBuilder before2020QueryBuilder = QueryBuilders.boolQuery();
                int nowYear = DateUtils.getYear(new Date());
                for (int startYear = 2020; startYear <= nowYear; startYear++) {
                    before2020QueryBuilder.mustNot(QueryBuilders.termQuery("fn_ModiDate", startYear));
                }
                boolQueryBuilder.should(before2020QueryBuilder);
            } else {
                boolQueryBuilder.should(QueryBuilders.termQuery("fn_ModiDate", modiDate));
            }
        }
        return boolQueryBuilder;
    }

    // 根据标准状态构建布尔查询条件
    private BoolQueryBuilder buildStatusQuery(List<String> statusList) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        for (String status : statusList) {
            boolQueryBuilder.should(QueryBuilders.termQuery("fn_StandStatus.keyword", status));
        }
        return boolQueryBuilder;
    }


    // 构建高亮显示的配置信息，设置需要高亮的字段以及高亮的前缀、后缀标签等
    private HighlightBuilder buildHighlightBuilder() {
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.requireFieldMatch(true) // 允许高亮显示非查询字段
                .field("fn_A301") // 设置需要高亮的字段名称
                .field("fn_A100")
                .preTags(PRE_TAG) // 高亮前缀标签
                .postTags(POST_TAG); // 高亮后缀标签
        return highlightBuilder;
    }

    // 处理搜索返回的响应结果，解析聚合数据和命中的数据，封装到BasicResult对象中返回
    private BasicResult processSearchResponse(SearchResponse response, BasicSearch basicSearch) {
        Aggregations aggregations = response.getAggregations();
        List<Aggregation> aggregationList = aggregations.asList();
        BasicResult basicResult = new BasicResult();
        // 处理聚合数据，遍历每个聚合结果，解析并封装到StandardClass对象中
        List<StandardClass> standardClassList = new ArrayList<>();
        aggregationList.forEach((aggregation -> {
            Terms categoryAggResult = (Terms) aggregation;
            categoryAggResult.getBuckets().forEach((bucket -> {
                StandardClass standardClass = new StandardClass();
                String fisrtCode = bucket.getKeyAsString();
                standardClass.setClassCode(fisrtCode);
                if (StringUtil.equals(fisrtCode, "CN")) {
                    standardClass.setClassName(redisUtil.hGet("STANDARDPRESSPLAN:CLASS", "GB"));
                } else {
                    standardClass.setClassName(redisUtil.hGet("STANDARDPRESSPLAN:CLASS", bucket.getKeyAsString()));
                }
                standardClass.setTotal(bucket.getDocCount());
                Aggregations aggregations1 = bucket.getAggregations();
                List<Aggregation> aggregationList1 = aggregations1.asList();
                List<Child> childList = new ArrayList<>();
                aggregationList1.forEach(aggregation1 -> {
                    Terms categoryAggResult1 = (Terms) aggregation1;
                    Map<String, Long> classMap = new HashMap<>();
                    categoryAggResult1.getBuckets().forEach(bucket1 -> {
                        String classStr = bucket1.getKeyAsString();
                        if (classStr.contains(";")) {
                            String[] calsses = classStr.split(";");
                            for (String calss : calsses) {
                                if (StringUtil.notEmpty(basicSearch.getClassCode())) {
                                    if (calss.equals(basicSearch.getClassCode())) {
                                        classMap.put(calss, StringUtil.toLong(classMap.get(calss), 0L) + bucket1.getDocCount());
                                    }
                                } else {
                                    classMap.put(calss, StringUtil.toLong(classMap.get(calss), 0L) + bucket1.getDocCount());
                                }
                            }
                        } else {
                            classMap.put(classStr, StringUtil.toLong(classMap.get(classStr), 0L) + bucket1.getDocCount());
                        }
                    });
                    for (String classkey : classMap.keySet()) {
                        if (StringUtil.equals(fisrtCode, "CN") && StringUtil.equals(aggregation1.getName(), "fn_CCSAgg")) {
                            String className = redisUtil.hGet("STANDARDPRESSPLAN:CLASS:GB:SECOND", classkey);
                            Child child = new Child();
                            child.setClassName(StringUtil.toDefaultString(className, "其他"));
                            child.setClassCode(classkey);
                            child.setTotal(classMap.get(classkey));
                            childList.add(child);

                        } else if (!StringUtil.equals(fisrtCode, "CN") && StringUtil.equals(aggregation1.getName(), "fn_TypeAgg")) {
                            String className = redisUtil.hGet("STANDARDPRESSPLAN:CLASS:" + fisrtCode + ":SECOND", classkey);
                            Child child = new Child();
                            child.setClassName(StringUtil.toDefaultString(className, "其他"));
                            child.setClassCode(classkey);
                            child.setTotal(classMap.get(classkey));
                            childList.add(child);
                        }

                    }


                });
                standardClass.setChild(childList);
                standardClassList.add(standardClass);
            }));
        }));

        // 处理命中的数据，遍历每个命中的文档，提取相关字段封装到StandardDatas对象中，并添加到数据列表
        SearchHits hits = response.getHits();
        Page page = new Page();
        page.setPageNo(Long.valueOf(basicSearch.getPageNo()));
        page.setPageSize(Long.valueOf(basicSearch.getPageSize()));
        if (hits.getTotalHits() != null) {
            page.setTotal(hits.getTotalHits().value);
        }

        List<StandardDatas> data = new ArrayList<>();
        for (SearchHit hit : hits.getHits()) {
            String standardNo = hit.getSourceAsMap().get("fn_A100").toString();
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            // 遍历高亮字段并更新原始数据中的对应字段值，将高亮后的文本替换原始文本
            for (Map.Entry<String, HighlightField> entry : highlightFields.entrySet()) {
                Text[] fragments = entry.getValue().getFragments();
                for (Text fragment : fragments) {
                    hit.getSourceAsMap().put(entry.getKey(), fragment.string());
                }
            }
            StandardDatas standardDatas = createStandardDatas(hit, standardNo);
            data.add(standardDatas);
        }

        basicResult.setData(data);
        basicResult.setPage(page);
        basicResult.setStandardClass(standardClassList);
        return basicResult;
    }


    // 根据搜索命中的文档数据创建StandardDatas对象，封装相关字段信息
    private StandardDatas createStandardDatas(SearchHit hit, String standardNo) {
        StandardDatas standardDatas = new StandardDatas();
        standardDatas.setId(StringUtil.toLong(hit.getSourceAsMap().get("id"), 0L));
        standardDatas.setStandardNo(hit.getSourceAsMap().get("fn_A100").toString());
        standardDatas.setTitle(StringUtil.toString(hit.getSourceAsMap().get("fn_A301")));
        standardDatas.setStatus(StringUtil.toString(hit.getSourceAsMap().get("fn_StandStatus")));
        standardDatas.setImpDate(StringUtil.toString(hit.getSourceAsMap().get("fn_A205")));
        standardDatas.setPubTime(StringUtil.toString(hit.getSourceAsMap().get("fn_ModiDate")));
        String fn_StandClass = StringUtil.toString(hit.getSourceAsMap().get("fn_StandClass"));
        String fn_AdoptNo = StringUtil.toString(hit.getSourceAsMap().get("fn_AdoptNo"));
        String pdfPath = redisUtil.hGet("STANDARD:FILEPATH", StringUtil.md5(standardNo));
        if (StringUtil.notEmpty(pdfPath)) {
            if (hit.getSourceAsMap().get("fn_price") != null) { // 增加标准价格
                standardDatas.setPrice((double) hit.getSourceAsMap().get("fn_price"));
            } else {
                standardDatas.setPrice(35.0);
            }
            if (fn_StandClass.equals("CN") && StringUtil.isEmpty(fn_AdoptNo)) {
                standardDatas.setFree(true);
            }
        }
        return standardDatas;
    }

    private void addSortRedis(BasicSearch basicSearch) {
        if (StringUtil.notEmpty(basicSearch.getClassCode()) && StringUtil.notEmpty(basicSearch.getStandardClass())) {
            if ("CN".equals(basicSearch.getStandardClass())) {

            }
            String className = redisUtil.hGet("STANDARDPRESSPLAN:CLASS:" + ("CN".equals(basicSearch.getStandardClass()) ? "GB" : basicSearch.getStandardClass()) + ":SECOND", basicSearch.getClassCode());
            className = StringUtil.toDefaultString(className, "其他");
            redisUtil.incrementKeyword("SORT:STANDARDSEARCH:CLASSCODE:" + ("CN".equals(basicSearch.getStandardClass()) ? "GB" : basicSearch.getStandardClass()), className);
        }
    }


}
